package com.rs.game;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;

import com.rs.Launcher;
import com.rs.Settings;
import com.rs.cores.CoresManager;
import com.rs.game.item.FloorItem;
import com.rs.game.item.Item;
import com.rs.game.npc.NPC;
import com.rs.game.player.Player;
import com.rs.game.player.Skills;
import com.rs.game.player.controlers.Wilderness;
import com.rs.utils.Utils;

public final class World {

	public static int exiting_delay;
	public static long exiting_start;
	
    private static final EntityList<Player> players = new EntityList<Player>(Settings.PLAYERS_LIMIT);
    private static final EntityList<NPC> npcs = new EntityList<NPC>(Settings.NPCS_LIMIT);
    private static final ConcurrentHashMap<Integer, Region> regions = new ConcurrentHashMap<Integer, Region>();
    
    public static final void init() {
    	addRestoreRunEnergyTask();
    	addRestoreHitPointsTask();
    	addRestoreSkillsTask();
    	addRestoreSpecialAttackTask();
    }
    
    private static final void addRestoreSpecialAttackTask() {
    	CoresManager.fastExecutor.scheduleWithFixedDelay(new Runnable() {
			@Override
			public void run() {
				synchronized(players) {
					for(Player player : players) {
						if(player == null || player.isDead() || !player.isRunning())
							continue;
						player.getCombatDefinitions().restoreSpecialAttack();
					}
				}
			}
    	}, 0, 30, TimeUnit.SECONDS);
    }
    
    private static final void addRestoreRunEnergyTask() {
    	CoresManager.fastExecutor.scheduleWithFixedDelay(new Runnable() {
			@Override
			public void run() {
				synchronized(players) {
					for(Player player : players) {
						if(player == null || player.isDead() || !player.isRunning())
							continue;
						player.restoreRunEnergy();
					}
				}
			}
    	}, 0, 2, TimeUnit.SECONDS);
    }
    
    private static final void addRestoreHitPointsTask() {
    	CoresManager.fastExecutor.scheduleAtFixedRate(new Runnable() {
			@Override
			public void run() {
					for(Player player : players) {
						if(player == null || player.isDead() || !player.isRunning())
							continue;
						player.restoreHitPoints();
					}
					for(NPC npc : npcs) {
						if(npc == null || npc.isDead() || npc.hasFinished())
							continue;
						npc.restoreHitPoints();
					}
			}
		}, 0, 6, TimeUnit.SECONDS);
    }
    
    private static final void addRestoreSkillsTask() {
    	CoresManager.fastExecutor.scheduleAtFixedRate(new Runnable() {
			@Override
			public void run() {
					for(Player player : players) {
						if(player == null || !player.isRunning())
							continue;
						int ammountTimes = player.getPrayer().usingPrayer(0, 8) ? 2 : 1;
						if(player.isResting())
							ammountTimes += 1;
						boolean berserker = player.getPrayer().usingPrayer(1, 5);
						for(int skill = 0; skill < 25; skill++) {
							if(skill == Skills.HITPOINTS || skill == Skills.PRAYER || skill == Skills.SUMMONING) 
								continue;
							for(int time = 0; time < ammountTimes; time++) {
							int currentLevel = player.getSkills().getLevel(skill);
							int normalLevel = player.getSkills().getLevelForXp(skill);
							if(currentLevel > normalLevel) {
								if(skill == Skills.ATTACK || skill == Skills.STRENGTH || skill == Skills.DEFENCE || skill == Skills.RANGE || skill == Skills.MAGIC) {
									if(berserker && Utils.getRandom(100) <= 15)
										continue;
								}
								player.getSkills().set(skill, currentLevel - 1);
							} else if (currentLevel < normalLevel)
								player.getSkills().set(skill, currentLevel + 1);
							else
								break;
							}
						}
					}
			}
    	}, 0, 30, TimeUnit.SECONDS);
    	
    }
    
    public static final ConcurrentHashMap<Integer, Region> getRegions() {
    		return regions;
    }
    
    public static final Region getRegion(int id) {
    	Region region = regions.get(id);
    	if(region == null) {
    		region = new Region(id);
    		regions.put(id, region);
    	}
		return region;
    }
    
	public static final void addPlayer(Player player) {
		players.add(player);
	}
	
	public static final void removePlayer(Player player) {
		players.remove(player);
	}
	
	public static final void addNPC(NPC npc) {
		npcs.add(npc);
	}
	
	public static final void removeNPC(NPC npc) {
		npcs.remove(npc);
	}
	
	public static final void spawnNPC(int id, WorldTile tile, int mapAreaNameHash, boolean canBeAttackFromOutOfArea) {
		new NPC(id, tile, mapAreaNameHash, canBeAttackFromOutOfArea);
	}
	
	/*
	 * check if the entity region changed because moved or teled then we update it
	 */
	public static final void updateEntityRegion(Entity entity) {
		if(entity.hasFinished()) {
			if(entity instanceof Player)
				getRegion(entity.getLastRegionId()).removePlayerIndex(entity.getIndex());
			else
				getRegion(entity.getLastRegionId()).removeNPCIndex(entity.getIndex());
			return;
		}
		int regionId = entity.getRegionId();
		if(entity.getLastRegionId() != regionId) { //map region entity at changed
			if(entity instanceof Player) {
				if(entity.getLastRegionId() > 0)
					getRegion(entity.getLastRegionId()).removePlayerIndex(entity.getIndex());
				Region region = getRegion(regionId);
				region.addPlayerIndex(entity.getIndex());
				Player player = (Player) entity;
				player.checkMultiArea();
				player.getControlerManager().moved();
				if(region.getMusicId() > 0) 
					if(player.getMusicId() != region.getMusicId())
						player.setMusicId(region.getMusicId());
			}else{
				if(entity.getLastRegionId() > 0)
					getRegion(entity.getLastRegionId()).removeNPCIndex(entity.getIndex());
				getRegion(regionId).addNPCIndex(entity.getIndex());
			}
			entity.setLastRegionId(regionId);
		}else{
			if(entity instanceof Player) {
				Player player = (Player) entity;
				player.checkMultiArea();
				player.getControlerManager().moved();
				if(player.getMusicId() > 0 && player.getMusicDelay() + (240000) < System.currentTimeMillis())
						player.setMusicId(player.getMusicId());
			}
		}
	}
	
	private static int getMask(int plane, int x, int y) {
		WorldTile tile = new WorldTile(x, y, plane);
		int regionId = tile.getRegionId();
		Region region = regions.get(regionId);
		if(region == null)
			return 2097152;
		int baseLocalX = x - ((regionId >> 8) * 64) ;
		int baseLocalY = y - ((regionId & 0xff) * 64);
		return region.getMask(tile.getPlane(), baseLocalX, baseLocalY);
	}
	
	public static final boolean checkWalkStep(int plane, int x, int y, int dir, int size) {
		int xOffset = Utils.DIRECTION_DELTA_X[dir];
		int yOffset = Utils.DIRECTION_DELTA_Y[dir];
		if(size == 1) {
			int mask = getMask(plane,x+xOffset,y+yOffset);
			if(xOffset == -1 && yOffset == 0)
				return (mask & 0x42240000) == 0;
			if(xOffset == 1 && yOffset == 0)
				return (mask & 0x60240000) == 0;
			if(xOffset == 0 && yOffset == -1)
				return (mask & 0x40a40000) == 0;
			if(xOffset == 0 && yOffset == 1)
				return (mask & 0x48240000) == 0;
			if(xOffset == -1 && yOffset == -1) {
				return (mask & 0x43a40000) == 0
						&& (getMask(plane, x-1, y) & 0x42240000) == 0
						&& (getMask(plane, x, y-1) & 0x40a40000) == 0;
			}
			if(xOffset == 1 && yOffset == -1) {
				return (mask & 0x60e40000) == 0
						&& (getMask(plane, x+1, y) & 0x60240000) == 0
						&& (getMask(plane, x, y-1) & 0x40a40000) == 0;
			}
			if(xOffset == -1 && yOffset == 1) {
				return (mask & 0x4e240000) == 0
						&& (getMask(plane, x-1, y) & 0x42240000) == 0
						&& (getMask(plane, x, y+1) & 0x48240000) == 0;
			}
			if(xOffset == 1 && yOffset == 1) {
				return (mask & 0x78240000) == 0
						&& (getMask(plane, x+1, y) & 0x60240000) == 0
						&& (getMask(plane, x, y+1) & 0x48240000) == 0;
			}
		}else if (size == 2) {
			if(xOffset == -1 && yOffset == 0)
				return (getMask(plane, x-1, y) & 0x43a40000) == 0
				&& (getMask(plane, x-1, y+1) & 0x4e240000) == 0;
			if(xOffset == 1 && yOffset == 0)
				return (getMask(plane, x+2, y) & 0x60e40000) == 0
				&& (getMask(plane, x+2, y+1) & 0x78240000) == 0;
			if(xOffset == 0 && yOffset == -1)
				return (getMask(plane, x, y-1) & 0x43a40000) == 0
				&& (getMask(plane, x+1, y-1) & 0x60e40000) == 0;
			if(xOffset == 0 && yOffset == 1)
				return (getMask(plane, x, y+2) & 0x4e240000) == 0
				&& (getMask(plane, x+1, y+2) & 0x78240000) == 0;
			if(xOffset == -1 && yOffset == -1)
				return (getMask(plane, x-1, y) & 0x4fa40000) == 0
				&& (getMask(plane, x-1, y-1) & 0x43a40000) == 0
				&& (getMask(plane, x, y-1) & 0x63e40000) == 0;
			if(xOffset == 1 && yOffset == -1)
				return (getMask(plane, x+1, y-1) & 0x63e40000) == 0
				&& (getMask(plane, x+2, y-1) & 0x60e40000) == 0
				&& (getMask(plane, x+2, y) & 0x78e40000) == 0;
			if(xOffset == -1 && yOffset == 1)
				return (getMask(plane, x-1, y+1) & 0x4fa40000) == 0
				&& (getMask(plane, x-1, y+1) & 0x4e240000) == 0
				&& (getMask(plane, x, y+2) & 0x7e240000) == 0;
			if(xOffset == 1 && yOffset == 1)
				return (getMask(plane, x+1, y+2) & 0x7e240000) == 0
				&& (getMask(plane, x+2, y+2) & 0x78240000) == 0
				&& (getMask(plane, x+1, y+1) & 0x78e40000) == 0;
		}else{
			if(xOffset == -1 && yOffset == 0) {
				if((getMask(plane, x-1, y) & 0x43a40000) != 0
						|| (getMask(plane, x-1, -1 + (y + size)) & 0x4e240000) != 0)
					return false;
				for (int sizeOffset = 1; sizeOffset < size - 1; sizeOffset++)
					if((getMask(plane, x-1, y+sizeOffset) & 0x4fa40000) != 0)
							return false;
			}else if(xOffset == 1 && yOffset == 0) {
				if((getMask(plane, x + size, y) & 0x60e40000) != 0
						|| (getMask(plane, x + size, y - (-size + 1)) & 0x78240000) != 0)
					return false;
				for (int sizeOffset = 1; sizeOffset < size - 1; sizeOffset++)
					if((getMask(plane, x+size, y+sizeOffset) & 0x78e40000) != 0)
							return false;
			}else if(xOffset == 0 && yOffset == -1) {
				if((getMask(plane, x, y-1) & 0x43a40000) != 0
						|| (getMask(plane, x + size - 1, y - 1) & 0x60e40000) != 0)
					return false;
				for (int sizeOffset = 1; sizeOffset < size - 1; sizeOffset++)
					if((getMask(plane, x+size, y-1) & 0x63e40000) != 0)
							return false;
			}else if(xOffset == 0 && yOffset == 1) {
				if((getMask(plane, x, y+size) & 0x4e240000) != 0
						|| (getMask(plane, x + (size - 1), y +size) & 0x78240000) != 0)
					return false;
				for (int sizeOffset = 1; sizeOffset < size - 1; sizeOffset++)
					if((getMask(plane, x+sizeOffset, y+size) & 0x7e240000) != 0)
							return false;
			}else if(xOffset == -1 && yOffset == -1) {
				if((getMask(plane, x-1, y-1) & 0x43a40000) != 0)
					return false;
				for (int sizeOffset = 1; sizeOffset < size; sizeOffset++)
					if((getMask(plane, x-1, y + (-1 + sizeOffset)) & 0x4fa40000) != 0
						|| (getMask(plane, sizeOffset - 1 + x, y - 1) & 0x63e40000) != 0)
							return false;
			}else if(xOffset == 1 && yOffset == -1) {
				if((getMask(plane, x+size, y-1) & 0x60e40000) != 0)
					return false;
				for (int sizeOffset = 1; sizeOffset < size; sizeOffset++)
					if((getMask(plane, x+size, sizeOffset + (-1 + y)) & 0x78e40000) != 0
						|| (getMask(plane, x + sizeOffset, y - 1) & 0x63e40000) != 0)
							return false;
			}else if(xOffset == -1 && yOffset == 1) {
				if((getMask(plane, x-1, y+size) & 0x4e240000) != 0)
					return false;
				for (int sizeOffset = 1; sizeOffset < size; sizeOffset++)
					if((getMask(plane, x-1, y + sizeOffset) & 0x4fa40000) != 0
						|| (getMask(plane, -1 + (x + sizeOffset), y + size) & 0x7e240000) != 0)
							return false;
			}else if(xOffset == 1 && yOffset == 1) {
				if((getMask(plane, x+size, y+size) & 0x78240000) != 0)
					return false;
				for (int sizeOffset = 1; sizeOffset < size; sizeOffset++)
					if((getMask(plane, x+sizeOffset, y + size) & 0x7e240000) != 0
						|| (getMask(plane, x+size, y + sizeOffset) & 0x78e40000) != 0)
							return false;
			}
		}
		return true;
	}
	
	
	public static final boolean containsPlayer(String username) {
			for(Player p2 : players) {
				if(p2 == null)
					continue;
				if(p2.getUsername().equals(username))
					return true;
			}
		return false;
	}
	
    
	public static final EntityList<Player> getPlayers() {
		return players;
	}
    
	public static final EntityList<NPC> getNPCs() {
		return npcs;
	}
    
    private World() {
    	
    }
    
	
	public static final void safeShutdown(final boolean restart, int delay) {
		if(exiting_start != 0)
			return;
		exiting_start = System.currentTimeMillis();
		exiting_delay = (int) delay;
		for(Player player : World.getPlayers()) {
			if(player == null || !player.hasStarted() || player.hasFinished())
				continue;
			player.getPackets().sendSystemUpdate(delay);
		}
		CoresManager.slowExecutor.schedule(new Runnable() {
			@Override
			public void run() {
				Launcher.saveAccounts();
				Launcher.shutdown();
				if(restart)
					Launcher.restart();
			}
		}, delay, TimeUnit.SECONDS);
	}
	
	public static final void spawnTemporaryObject(final WorldObject object, long time) {
		final int regionId = object.getRegionId();
		WorldObject realMapObject = getRegion(regionId).getRealObject(object);
		//remakes object, has to be done because on static region coords arent same of real
		final WorldObject realObject = new WorldObject(realMapObject.getId(), realMapObject.getType(), realMapObject.getRotation(), object.getX(), object.getY(), object.getPlane());
		spawnObject(object, false);
		CoresManager.slowExecutor.schedule(new Runnable() {
			@Override
			public void run() {
				synchronized(players) {
					getRegion(regionId).removeObject(object);
					for(Player p2 : players) {
						if(p2 == null || !p2.hasStarted() || p2.hasFinished() || p2.getPlane() != object.getPlane() || !p2.getMapRegionsIds().contains(regionId))
							continue;
						if(realObject != null)
							p2.getPackets().sendSpawnedObject(realObject);
						else {
							//TODO DESTROY
						}
					}
				}
			}
			
		}, time, TimeUnit.MILLISECONDS);
	}
	
	public static final void spawnObject(WorldObject object, boolean clip) {
		int regionId = object.getRegionId();
		getRegion(regionId).addObject(object);
		if(clip) {
			int baseLocalX = object.getX() - ((regionId >> 8) * 64) ;
			int baseLocalY = object.getY() - ((regionId & 0xff) * 64);
			getRegion(regionId).addMapObject(object, baseLocalX, baseLocalY);
		}
		synchronized(players) {
			for(Player p2 : players) {
				if(p2 == null || !p2.hasStarted() || p2.hasFinished() || p2.getPlane() != object.getPlane() || !p2.getMapRegionsIds().contains(regionId))
					continue;
				p2.getPackets().sendSpawnedObject(object);
			}
		}
	}
	
	public static final void addGroundItem(Item item, final WorldTile tile, final Player owner/*null for default*/, final boolean underGrave, long hiddenTime/*default 3minutes*/, boolean invisible) {
		final FloorItem floorItem = new FloorItem(item, tile, owner, owner == null ? false : underGrave, invisible);
		final Region region = getRegion(tile.getRegionId());
		region.forceGetFloorItems().add(floorItem);
		
		if(invisible && hiddenTime != -1) {
			if(owner != null)
				owner.getPackets().sendGroundItem(floorItem);
			CoresManager.slowExecutor.schedule(new Runnable() {
				@Override
				public void run() {
					if(!region.forceGetFloorItems().contains(floorItem))
						return;
					int regionId = tile.getRegionId();
					if(owner != null && underGrave) {
						region.forceGetFloorItems().remove(floorItem);
						if(owner.getMapRegionsIds().contains(regionId) || owner.getPlane() != tile.getPlane())
							owner.getPackets().sendRemoveGroundItem(floorItem);
						return;
					}
					floorItem.setInvisible(false);
					for(Player player : players) {
						if(player == null || player == owner || !player.hasStarted() || player.hasFinished() || player.getPlane() != tile.getPlane() || !player.getMapRegionsIds().contains(regionId))
							continue;
						player.getPackets().sendGroundItem(floorItem);
					}
					removeGroundItem(floorItem, 180);
				}
			}, hiddenTime, TimeUnit.SECONDS);
			return;
		}
		int regionId = tile.getRegionId();
		for(Player player : players) {
			if(player == null || !player.hasStarted() || player.hasFinished() || player.getPlane() != tile.getPlane() || !player.getMapRegionsIds().contains(regionId))
				continue;
			player.getPackets().sendGroundItem(floorItem);
		}
		removeGroundItem(floorItem, 180);
	}
	
	public static final void updateGroundItem(Item item, final WorldTile tile, final Player owner) {
		final FloorItem floorItem = World.getRegion(tile.getRegionId()).getGroundItem(item.getId(), tile, owner);
		if(floorItem == null) {
			addGroundItem(item, tile, owner, false, 360, true);
			return;
		}
		floorItem.setAmount(floorItem.getAmount()+item.getAmount());
		owner.getPackets().sendRemoveGroundItem(floorItem);
		owner.getPackets().sendGroundItem(floorItem);
			
	}
	private static final void removeGroundItem(final FloorItem floorItem, long publicTime) {
		CoresManager.slowExecutor.schedule(new Runnable() {
			@Override
			public void run() {
				int regionId = floorItem.getTile().getRegionId();
				Region region = getRegion(regionId);
				if(!region.forceGetFloorItems().contains(floorItem))
					return;
				region.forceGetFloorItems().remove(floorItem);
				for(Player player : World.getPlayers()) {
					if(player == null || !player.hasStarted() || player.hasFinished() || player.getPlane() != floorItem.getTile().getPlane() || !player.getMapRegionsIds().contains(regionId))
						continue;
					player.getPackets().sendRemoveGroundItem(floorItem);
				}
			}
		}, publicTime, TimeUnit.SECONDS);
	}
	
	public static final boolean removeGroundItem(Player player, FloorItem floorItem) {
		int regionId = floorItem.getTile().getRegionId();
		Region region = getRegion(regionId);
		if(!region.forceGetFloorItems().contains(floorItem))
			return false;
		if(player.getInventory().getFreeSlots() == 0)
			return false;
		region.forceGetFloorItems().remove(floorItem);
		player.getInventory().addItem(floorItem.getId(), floorItem.getAmount());
		if(floorItem.isInvisible() || floorItem.isGrave()) {
			player.getPackets().sendRemoveGroundItem(floorItem);
			return true;
		}else{
			for(Player p2 : World.getPlayers()) {
				if(player == null || !player.hasStarted() || player.hasFinished() || player.getPlane() != floorItem.getTile().getPlane() || !player.getMapRegionsIds().contains(regionId))
					continue;
				p2.getPackets().sendRemoveGroundItem(floorItem);
			}
			return true;
		}
	}
	
	public static final void sendGraphics(Entity creator, Graphics graphics, WorldTile tile) {
		for(int regionId : creator.getMapRegionsIds()) {
			CopyOnWriteArrayList<Integer> playersIndexes = getRegion(regionId).getPlayerIndexes();
			if(playersIndexes == null)
				continue;
			for(Integer playerIndex : playersIndexes) {
				Player player = players.get(playerIndex);
				if(player == null
						|| !player.hasStarted()
						|| player.hasFinished()
						|| !player.withinDistance(tile))
					continue;
				player.getPackets().sendGraphics(graphics, tile);
			}
		}
	}
	
	public static final void sendProjectile(Entity shooter, WorldTile receiver, int gfxId, int startHeight, int endHeight, int speed, int delay,  int curve, int startDistanceOffset) {
		for(int regionId : shooter.getMapRegionsIds()) {
			CopyOnWriteArrayList<Integer> playersIndexes = getRegion(regionId).getPlayerIndexes();
			if(playersIndexes == null)
				continue;
			for(Integer playerIndex : playersIndexes) {
				Player player = players.get(playerIndex);
				if(player == null
						|| !player.hasStarted()
						|| player.hasFinished()
						|| (!player.withinDistance(shooter) && !player.withinDistance(receiver)))
					continue;
				player.getPackets().sendProjectile(null, shooter, receiver, gfxId, startHeight, endHeight, speed, delay, curve, startDistanceOffset, shooter.getSize());
			}
		}
	}
	
	public static final void sendProjectile(Entity shooter, Entity receiver, int gfxId, int startHeight, int endHeight, int speed, int delay,  int curve, int startDistanceOffset) {
		for(int regionId : shooter.getMapRegionsIds()) {
			CopyOnWriteArrayList<Integer> playersIndexes = getRegion(regionId).getPlayerIndexes();
			if(playersIndexes == null)
				continue;
			for(Integer playerIndex : playersIndexes) {
				Player player = players.get(playerIndex);
				if(player == null
						|| !player.hasStarted()
						|| player.hasFinished()
						|| (!player.withinDistance(shooter) && !player.withinDistance(receiver)))
					continue;
				player.getPackets().sendProjectile(receiver, shooter, receiver, gfxId, startHeight, endHeight, speed, delay, curve, startDistanceOffset, shooter.getSize());
			}
		}
	}
	
	public static final boolean isMultiArea(WorldTile tile) {
		return true; 
	}
	
	public static final boolean isPvpArea(WorldTile tile) {
		return Wilderness.isAtWild(tile); 
	}
	
	
}
